#region License, Terms and Conditions
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
#endregion
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Web.Mvc;
using facebook.Schema;
using pesta.Data;
using pesta.Data.Model.Helpers;
using Pesta.DataAccess;
using raya.Models.activities;
using Pesta.Utilities.Helpers;
using raya.Library;
using raya.Library.Extensions;
using raya.Library.Helpers;
using raya.Models.applications;
using raya.Models.login;
using raya.Models.notifications;
#if AZURE
using Pesta.DataAccess.Azure;
#else
using Pesta.DataAccess.SqlServer;
#endif

namespace raya.Models.people
{

    public class PersonSearchResult
    {
        public HashSet<login.login> results { get; set; }
        public HashSet<login.login> friends { get; set; }
        public string error { get; set; }
    }
    public class PersonRaya 
    {
        public login.login loginInfo { get; set; }
        public Person personInfo { get; set; }
    }
    
    public class ProfileApplication
    {
        public Application app { get; set; }
        public string mod_id { get; set; }
        public NameValueCollection user_prefs { get; set; }
        public oauth.oauth.OAuthConsumerInfo oauth { get; set; }
    }
    public class Profile : ProfileBase
    {
        public List<ActivityRaya> activities { get; set; }
        public ProfileNotification notifications { get; set; }
        public int page { get; set; }
        public int pages { get; set; }
        public int friendsCount { get; set; }
        public int unconnectedFBFriendsCount { get; set; }
        public List<login.login> friends { get; set; }
        public List<login.login> newFriends { get; set; } // for friend suggestion
        public string message { get; set; }
#if AZURE
        public List<ImageRow> photos { get; set; }
#else
        public List<image> photos { get; set; }
#endif
        public List<ProfileApplication> applications { get; set; }
        // selectLists
        public SelectList drinkerList 
        { 
            get
            {
                if (person.personInfo.drinker.HasValue)
                {
                    return person.personInfo.drinker.ToSelectList();
                }
                return typeof(Drinker).ToSelectList();
            } 
            set { }
        }
        public SelectList smokerList
        {
            get
            {
                if (person.personInfo.smoker.HasValue)
                {
                    return person.personInfo.smoker.ToSelectList();
                }
                return typeof(Smoker).ToSelectList();
            }
            set { }
        }
        public SelectList relationshipList
        {
            get
            {
                if (!string.IsNullOrEmpty(person.personInfo.relationshipStatus))
                {
                    return ((Enums.relationship)person.personInfo.relationshipStatus.ToEnum<Enums.relationship>()).ToSelectList();
                }
                return typeof(Enums.relationship).ToSelectList();
            }
            set { }
        }
        public List<CheckBoxListInfo> lookingForList
        {
            get
            {
                if (person.personInfo.lookingFor != null)
                {
                    return person.personInfo.lookingFor.ToCheckBoxList();
                }
                return new List<LookingFor>().ToCheckBoxList();
            }
            set { }
        }
        public SelectList livingArrangementList
        {
            get
            {
                if (!string.IsNullOrEmpty(person.personInfo.livingArrangement))
                {
                    return ((Enums.livingArrangement)person.personInfo.livingArrangement.ToEnum<Enums.livingArrangement>()).ToSelectList();
                }
                return typeof(Enums.livingArrangement).ToSelectList();
            }
            set { }
        }
        public SelectList ethnicityList
        {
            get
            {
                if (!string.IsNullOrEmpty(person.personInfo.ethnicity))
                {
                    return ((Enums.ethnicity)person.personInfo.ethnicity.ToEnum<Enums.ethnicity>()).ToSelectList();
                }
                return typeof(Enums.ethnicity).ToSelectList();
            }
            set { }
        }

        public Profile()
        {
#if AZURE
            photos = new List<ImageRow>();
#else
            photos = new List<image>();
#endif
        }
    }
    public class ProfileBase
    {
        public PersonRaya person { get; set; }
        public bool is_owner { get; set; }
        public bool is_friend { get; set; }
    }

    public class people : Model
    {
        public people()
        {
            
        }
        
        private static string NullStringIfHyphen(string value)
        {
            return value == "-" ? null : value;
        }

#if AZURE

        public PersonRaya GetPerson(string id)
        {
            return GetPerson(id, false);
        }
        // if extended = true, it also queries all child tables
        // defaults to false since its a hell of a presure on the database.
        // remove once we add some proper caching
        public PersonRaya GetPerson(string id, bool extended)
        {
            PersonRaya data = new PersonRaya();
            var ids = new HashSet<string>() { id };
            var fields = new HashSet<string> {"@all"};
            using (var db = new AzureDbFetcher())
            {
                var result = db.GetPeople(ids, fields, null);
                Person p;
                if (!result.TryGetValue(id, out p))
                {
                    return null;
                }
                data.personInfo = p;
                data.loginInfo = new login.login()
                {
                    emailAddress = p.emails.Count > 0 ? p.emails[0].value:"",
                    fbuid = p.fb_userid,
                    gfcUid = p.gfc_userid,
                    name = p.name.givenName + " " + p.name.familyName,
                    id = p.id,
                };

            }
            return data;
        }

        public bool IsFriend(string personId, string friendId)
        {
            using (var db = RayaDataContext.DB())
            {
                var result =
                    db.friends.Where(f => (f.friend_id == friendId && f.person_id == personId) ||
                                          (f.friend_id == personId && f.person_id == friendId)).
                        SingleOrDefault();
                if (result == null)
                {
                    return false;
                }
            }
            return true;
        }
        public HashSet<login.login> GetFriends(string id)
        {
            return GetFriends(id, 0, 0);
        }

        public HashSet<login.login> GetFriends(string pid, int start, int max)
        {
            var ret = new HashSet<login.login>();
            using (var db = RayaDataContext.DB())
            {
                var results = db.friends.Where(x => x.PartitionKey == pid || x.friend_id == pid);
                var friendList = new List<FriendRow>();
                foreach (var row in results)
                {
                    friendList.Add(row);
                }

                friendList = friendList.Skip(start).Take(max).ToList();

                if (max != 0)
                {
                    friendList = friendList.Skip(start).Take(max).ToList();
                }
                else
                {
                    friendList = friendList.Skip(start).ToList();
                }
                foreach (var result in friendList)
                {
                    if (result.PartitionKey == pid)
                    {
                        ret.Add(GetPersonInfo(result.friend_id));
                    }
                    else
                    {
                        ret.Add(GetPersonInfo(result.person_id));
                    }
                }
            }
            return ret;
        }

        public List<login.login> GetNewConnectedFBFriends(List<long> fbuids, string myuserid)
        {
            using (var db = RayaDataContext.DB())
            {
                var data = new List<login.login>();
                var friends = new List<string>();

                // gets all user's friends
                var f1 = db.friends.Where(x => x.friend_id == myuserid);
                var f2 = db.friends.Where(x => x.PartitionKey == myuserid);
                foreach (var row in f1)
                {
                    friends.Add(row.person_id);
                }
                foreach (var row in f2)
                {
                    friends.Add(row.friend_id);
                }

                foreach (var fbuid in fbuids)
                {
                    var p = db.persons.Where(x => x.fb_userid == fbuid).SingleOrDefault();
                    foreach (var friend in friends)
                    {
                        if (p.id == friend)
                            continue;
                        data.Add(p.ToLogin());
                    }
                }
                return data;
            }
        }

        public int GetFriendsCount(string id)
        {
            using (var db = RayaDataContext.DB())
            {
                var result = db.friends.Where(p => p.person_id == id || p.friend_id == id);
                int count = 0;
                foreach (var row in result)
                {
                    count++;
                }
                return count;
            }
        }

        public static IList<login.login> getRecentMembers(int number)
        {
            var data = new List<login.login>();
            using (var db = RayaDataContext.DB())
            {
                foreach (var p in db.persons)
                {
                    var entry = new login.login
                                    {
                                        id = p.id,
                                        emailAddress = p.email,
                                        name = p.first_name + " " + p.last_name,
                                        fbuid = p.fb_userid
                                    };
                    data.Add(entry);
                }
            }
            return data;
        }

        public bool RemoveFriend(string personId, string friendId)
        {
            using (var db = RayaDataContext.DB())
            {
                var friends =
                    db.friends.Where(
                        f =>
                        (f.friend_id == friendId && f.person_id == personId) ||
                        (f.friend_id == personId && f.person_id == friendId));
                db.DeleteAllOnSubmit(friends);
                db.SubmitChanges();
                return true;
            }
        }

        public bool AddFriendRequest(string id, string friendId)
        {
            try
            {
                NotificationRow req = new NotificationRow(id, string.Empty)
                {
                    recipient = friendId,
                    creator = id,
                    type = notifications.notifications.notifyType.friendRequest.ToInt()
                };
                using (var db = RayaDataContext.DB())
                {
                    db.InsertOnSubmit(AzureRayaDataContext.TableNames.notifications, req);
                    db.SubmitChanges();
                }
            }
            catch
            {
                return false;
            }
            return true;
        }

        public login.login GetPersonInfo(string id)
        {
            using (var db = RayaDataContext.DB())
            {
                var result = db.persons.Where(x => x.PartitionKey == id).SingleOrDefault();
                return new login.login()
                           {
                               id = result.id,
                               name = result.first_name + " " + result.last_name
                           };
            }
        }

        public void AcceptFriendRequest(string id, string friendId)
        {
            using (var db = RayaDataContext.DB())
            {
                // double check if a friend request actually exists (reversed friend/person since the request came from the other party)
                var req = db.notifications.Where(f => f.recipient == id &&
                                                     f.creator == friendId &&
                                                     f.type ==
                                                     (int)notifications.notifications.notifyType.friendRequest)
                    .SingleOrDefault();

                if (req == null)
                {
                    throw new Exception("Friend request does not exist");
                }
                db.DeleteOnSubmit(req);
                db.SubmitChanges();
                AddFriend(id, friendId);
            }
        }

        public void AddFriend(string id, string friendId)
        {
            using (var db = RayaDataContext.DB())
            {
                // check that an entry does not already exist
                var req = db.friends.Where(f => (f.friend_id == id && f.person_id == friendId) ||
                                                (f.friend_id == friendId && f.person_id == id)).SingleOrDefault();
                if (req != null)
                {
                    throw new Exception("Is already a friend");
                }
                // add new friend entry
                FriendRow entry = new FriendRow(id, friendId);
                db.InsertOnSubmit(AzureRayaDataContext.TableNames.friends, entry);
                db.SubmitChanges();

                var activities = new activities.activities();
                activities.addNewFriendActivity(friendId, id);
            }
        }

        public bool RejectFriendRequest(string id, string friendId)
        {
            try
            {
                using (var db = RayaDataContext.DB())
                {
                    var req = db.notifications.Where(f => f.creator == friendId &&
                                                           f.recipient == id &&
                                                           f.type ==
                                                           (int) notifications.notifications.notifyType.friendRequest)
                                        .SingleOrDefault();
                    db.DeleteOnSubmit(req);
                    db.SubmitChanges();
                }
            }
            catch
            {
                return false;
            }
            return true;
        }

        public void SetProfilePhoto(string pid, string url)
        {
            using (var db = RayaDataContext.DB())
            {
                var person = db.persons.Where(p => p.id == pid).SingleOrDefault();
                if (person != null)
                {
                    person.thumbnail_url = url;
                    db.UpdateOnSubmit(person);
                    db.SubmitChanges();
                }
            }
        }

        public void SavePerson(string pid, NameValueCollection values)
        {
            using (var db = RayaDataContext.DB())
            {
                // just update changes
                var data = db.persons.Where(x => x.PartitionKey == pid).Single();

                data.about_me = NullStringIfHyphen(values["about_me"]);
                data.children = NullStringIfHyphen(values["children"]);
                if (!PHPExt.empty(values["date_of_birth_month"]) &&
                    !PHPExt.empty(values["date_of_birth_day"]) &&
                    !PHPExt.empty(values["date_of_birth_year"]) &&
                    PHPExt.is_numeric(values["date_of_birth_month"]) &&
                    PHPExt.is_numeric(values["date_of_birth_day"]) &&
                    PHPExt.is_numeric(values["date_of_birth_year"]))
                {
                    data.date_of_birth = new DateTime(int.Parse(values["date_of_birth_year"]), int.Parse(values["date_of_birth_month"]), int.Parse(values["date_of_birth_day"]), 0, 0, 1).ToUnixTime();
                }
                if (!string.IsNullOrEmpty(values["lookingForList"]))
                {
                    var lookingForValues = values["lookingForList"].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var value in lookingForValues)
                    {
                        int val;
                        int.TryParse(value, out val);
                        data.looking_for |= val;
                    }
                }

                data.drinker = NullStringIfHyphen(values["drinker"]);
                data.ethnicity = NullStringIfHyphen(values["ethnicity"]);
                data.fashion = NullStringIfHyphen(values["fashion"]);
                data.gender = NullStringIfHyphen(values["gender"]);
                data.happiest_when = NullStringIfHyphen(values["happiest_when"]);
                data.humor = NullStringIfHyphen(values["humor"]);
                data.job_interests = NullStringIfHyphen(values["job_interests"]);
                data.living_arrangement = NullStringIfHyphen(values["living_arrangement"]);
                data.nickname = NullStringIfHyphen(values["nickname"]);
                data.pets = NullStringIfHyphen(values["pets"]);
                data.political_views = NullStringIfHyphen(values["political_views"]);
                data.profile_song = NullStringIfHyphen(values["profile_song"]);
                data.profile_video = NullStringIfHyphen(values["profile_video"]);
                data.relationship_status = NullStringIfHyphen(values["relationship_status"]);
                data.religion = NullStringIfHyphen(values["religion"]);
                data.romance = NullStringIfHyphen(values["romance"]);
                data.scared_of = NullStringIfHyphen(values["scared_of"]);
                data.sexual_orientation = NullStringIfHyphen(values["sexual_orientation"]);
                data.smoker = NullStringIfHyphen(values["smoker"]);
                data.status = NullStringIfHyphen(values["status"]);
                data.time_zone = !String.IsNullOrEmpty(values["time_zone"]) ? int.Parse(values["time_zone"]) : (int?)null;
                data.first_name = NullStringIfHyphen(values["first_name"]);
                data.last_name = NullStringIfHyphen(values["last_name"]);
                db.UpdateOnSubmit(data);
                db.SubmitChanges();
            }
        }

        public HashSet<login.login> Search(string name)
        {
            var ret = new HashSet<login.login>();
            using (var db = RayaDataContext.DB())
            {
                var persons = db.persons.Where(p => (p.first_name + " " + p.last_name) == name || p.email == name);
                foreach (var p in persons)
                {
                    var login = new login.login
                                    {
                                        id = p.id,
                                        name = p.first_name + " " + p.last_name,
                                    };
                    ret.Add(login);
                }
            }
            return ret;
        }

        public static void save_gfc_person(string pid, string gfcUid)
        {
            using (var db = RayaDataContext.DB())
            {
                var user = db.persons.Where(f => f.PartitionKey == pid).SingleOrDefault();
                if (user != null)
                {
                    user.gfc_id = gfcUid;
                    db.UpdateOnSubmit(user);
                    db.SubmitChanges();
                }
            }
        }

        public static void remove_fb_person(long fbuid)
        {
            using (var db = RayaDataContext.DB())
            {
                var u = db.persons.Where(x => x.fb_userid == fbuid).SingleOrDefault();
                if (u != null)
                {
                    db.DeleteOnSubmit(u);
                    db.SubmitChanges();    
                }
            }
        }

        public void RemovePerson(string id)
        {
            using (var db = RayaDataContext.DB())
            {
                var per = db.persons.Where(x => x.id == id).SingleOrDefault();
                if (per != null)
                {
                    db.DeleteOnSubmit(per);
                    db.SubmitChanges();
                }
            }
        }

#else
        public bool IsFriend(string personId, string friendId)
        {
            object data;
            string key = personId + " " + friendId;
            if (!TryGetCache("people", key, out data))
            {
                using (var db = RayaDataContext.DB())
                {
                    var exist = (from f in db.friends
                                    where
                                        (f.friend_id.ToString() == friendId && f.person_id.ToString() == personId) ||
                                        (f.friend_id.ToString() == personId && f.person_id.ToString() == friendId)
                                    select f.friend_id).SingleOrDefault();
                    add_dependency("people", personId.ToString(), "people_res:" + key);
                    add_dependency("people", friendId.ToString(), "people_res:" + key);
                    data = exist;
                    Insert("people", key, data);
                }
            }
            return data != null;
        }
        public bool RemoveFriend(string personId, string friendId)
        {
            invalidate_dependency("people", friendId);
            invalidate_dependency("peopleInfo", friendId);
            using (var db = RayaDataContext.DB())
            {
                var friends =
                    db.friends.Where(
                        f =>
                        (f.friend_id.ToString() == friendId && f.person_id.ToString() == personId) ||
                        (f.friend_id.ToString() == personId && f.person_id.ToString() == friendId));
                db.friends.DeleteAllOnSubmit(friends);
                db.SubmitChanges();
                return db.GetChangeSet().Deletes.Count == 0;
            }
        }
        public void SetProfilePhoto(string pid, string url)
        {
            invalidate_dependency("people", pid.ToString());
            invalidate_dependency("friendInfo", pid.ToString());
            using (var db = RayaDataContext.DB())
            {
                var person = db.persons.SingleOrDefault(p => p.id.ToString() == pid);
                person.thumbnail_url = url;
                db.SubmitChanges();
            }
        }
        public void SavePerson(int pid, NameValueCollection values)
        {
            invalidate_dependency("people", pid.ToString());
            invalidate_dependency("peopleInfo", pid.ToString());
            using (var db = RayaDataContext.DB())
            {
                // just update changes
                var data = (from p in db.persons
                            where p.id == pid
                           select p).Single();

                data.about_me = NullStringIfHyphen(values["about_me"]);
                data.children = NullStringIfHyphen(values["children"]);
                if (!PHPExt.empty(values["date_of_birth_month"]) &&
                    !PHPExt.empty(values["date_of_birth_day"]) &&
                    !PHPExt.empty(values["date_of_birth_year"]) &&
                    PHPExt.is_numeric(values["date_of_birth_month"]) &&
                    PHPExt.is_numeric(values["date_of_birth_day"]) &&
                    PHPExt.is_numeric(values["date_of_birth_year"]))
                {
                    data.date_of_birth = new DateTime(int.Parse(values["date_of_birth_year"]), int.Parse(values["date_of_birth_month"]), int.Parse(values["date_of_birth_day"]), 0, 0, 1).ToUnixTime();
                }
                if (!string.IsNullOrEmpty(values["lookingForList"]))
                {
                    var lookingForValues = values["lookingForList"].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (var value in lookingForValues)
                    {
                        int val;
                        int.TryParse(value, out val);
                        data.looking_for |= val;
                    }
                }
                
                data.drinker = NullStringIfHyphen(values["drinker"]);
                data.ethnicity = NullStringIfHyphen(values["ethnicity"]);
                data.fashion = NullStringIfHyphen(values["fashion"]);
                data.gender = NullStringIfHyphen(values["gender"]);
                data.happiest_when = NullStringIfHyphen(values["happiest_when"]);
                data.humor = NullStringIfHyphen(values["humor"]);
                data.job_interests = NullStringIfHyphen(values["job_interests"]);
                data.living_arrangement = NullStringIfHyphen(values["living_arrangement"]);
                data.nickname = NullStringIfHyphen(values["nickname"]);
                data.pets = NullStringIfHyphen(values["pets"]);
                data.political_views = NullStringIfHyphen(values["political_views"]);
                data.profile_song = NullStringIfHyphen(values["profile_song"]);
                data.profile_video = NullStringIfHyphen(values["profile_video"]);
                data.relationship_status = NullStringIfHyphen(values["relationship_status"]);
                data.religion = NullStringIfHyphen(values["religion"]);
                data.romance = NullStringIfHyphen(values["romance"]);
                data.scared_of = NullStringIfHyphen(values["scared_of"]);
                data.sexual_orientation = NullStringIfHyphen(values["sexual_orientation"]);
                data.smoker = NullStringIfHyphen(values["smoker"]);
                data.status = NullStringIfHyphen(values["status"]);
                data.time_zone = !String.IsNullOrEmpty(values["time_zone"]) ? int.Parse(values["time_zone"]) : (int?)null;
                data.first_name = NullStringIfHyphen(values["first_name"]);
                data.last_name = NullStringIfHyphen(values["last_name"]);

                db.SubmitChanges();
            }
        }
        public void RemovePerson(int id)
        {
            invalidate_dependency("people", id.ToString());
            invalidate_dependency("peopleInfo", id.ToString());
            using (var db = RayaDataContext.DB())
            {
                var per = db.persons.Where(x => x.id == id);
                db.persons.DeleteOnSubmit(per.SingleOrDefault());
                db.SubmitChanges();
            }
        }

        public static void remove_fb_person(long fbuid)
        {
            using (var db = RayaDataContext.DB())
            {
                var u = from f in db.fb_users
                             where f.fb_userid == fbuid
                             select f;
                db.fb_users.DeleteAllOnSubmit(u);
                db.SubmitChanges();
            }
        }
        
        public fb_user get_fb_person(int pid)
        {
            using (var db = RayaDataContext.DB())
            {
                return (from f in db.fb_users
                             where f.personid == pid
                             select f).SingleOrDefault();
            }
        }
        public PersonRaya GetPerson(string id)
        {
            return GetPerson(id, false);
        }
        // if extended = true, it also queries all child tables
        // defaults to false since its a hell of a presure on the database.
        // remove once we add some proper caching
        public PersonRaya GetPerson(string pid, bool extended)
        {
            int id = int.Parse(pid);
            //_extended = false;
            //TODO missing : person_languages_spoken, need to add table with ISO 639-1 codes
            object data;
            if (!TryGetCache("people", id.ToString(), out data))
            {
                using (var db = RayaDataContext.DB())
                {
                    var p = (from b in db.persons
                                  where b.id == id
                                  select b).SingleOrDefault();
                    if (p == null)
                    {
                        return null;
                    }
                    var personRaya = new PersonRaya();
                    personRaya.loginInfo = new login.login()
                                               {
                                                   emailAddress = p.email,
                                                   fbuid =
                                                       (p.fb_user != null
                                                            ? p.fb_user.fb_userid
                                                            : (long?) null),
                                                    name = p.first_name + " " + p.last_name,
                                                   id = p.id.ToString(),
                                               };

                    personRaya.personInfo = new Person()
                                                {
                                                    id = id.ToString(),
                                                    aboutMe = p.about_me,
                                                    age = p.age,
                                                    children = p.children,
                                                    birthday = p.date_of_birth.HasValue? p.date_of_birth.Value.FromUnixTime(): (DateTime?) null,
                                                    drinker =
                                                        (p.drinker == null
                                                             ? (Drinker?) null
                                                             : (Drinker)
                                                               Enum.Parse(typeof (Drinker), p.drinker, true)),
                                                    ethnicity = p.ethnicity,
                                                    fashion = p.fashion,
                                                    _gender = p.gender,
                                                    happiestWhen = p.happiest_when,
                                                    humor = p.humor,
                                                    jobInterests = p.job_interests,
                                                    livingArrangement = p.living_arrangement,
                                                    // should use bit fields
                                                    _lookingFor = p.looking_for,
                                                    nickname = p.nickname,
                                                    pets = p.pets,
                                                    politicalViews = p.political_views,
                                                    profileSong =
                                                        new Url()
                                                            {
                                                                value = p.profile_song,
                                                                type = MediaItem.Type.audio.ToString()
                                                            },
                                                    profileUrl = p.profile_url,
                                                    profileVideo =
                                                        new Url()
                                                            {
                                                                value = p.profile_video,
                                                                type = MediaItem.Type.video.ToString()
                                                            },
                                                    relationshipStatus = p.relationship_status,
                                                    religion = p.religion,
                                                    romance = p.romance,
                                                    scaredOf = p.scared_of,
                                                    sexualOrientation = p.sexual_orientation,
                                                    smoker =
                                                        p.smoker == null
                                                            ? (Smoker?) null
                                                            : (Smoker)
                                                              Enum.Parse(typeof (Smoker), p.smoker),
                                                    status = p.status,
                                                    thumbnailUrl = p.thumbnail_url,
                                                    utcOffset = p.time_zone,
                                                    name =
                                                        new Name(p.first_name,p.last_name),
                                                    activities = extended
                                                                     ?
                                                                         db.person_activities.Where(
                                                                             x => x.person_id == id)
                                                                             .Select(x => x.activity).
                                                                             ToList()
                                                                     : null,
                                                    bodyType = extended
                                                                   ?
                                                                       db.person_body_types.Where(
                                                                           x => x.person_id == id)
                                                                           .Select(x => new BodyType()
                                                                                            {
                                                                                                build
                                                                                                    =
                                                                                                    x.
                                                                                                    build,
                                                                                                eyeColor
                                                                                                    =
                                                                                                    x.
                                                                                                    eye_color,
                                                                                                hairColor
                                                                                                    =
                                                                                                    x.
                                                                                                    hair_color,
                                                                                                height
                                                                                                    =
                                                                                                    x.
                                                                                                    height,
                                                                                                weight
                                                                                                    =
                                                                                                    x.
                                                                                                    weight
                                                                                            })
                                                                           .SingleOrDefault()
                                                                   : null,
                                                    books =
                                                        extended
                                                            ? db.person_books.Where(
                                                                  x => x.person_id == id)
                                                                  .Select(x => x.book).ToList()
                                                            : null,
                                                    cars = extended
                                                               ?
                                                                   db.person_cars.Where(
                                                                       x => x.person_id == id)
                                                                       .Select(x => x.car).ToList()
                                                               : null,
                                                    emails = extended
                                                                 ?
                                                                     db.person_emails.Where(
                                                                         x => x.person_id == id)
                                                                         .Select(x => new ListField()
                                                                                          {
                                                                                              value =
                                                                                                  x.
                                                                                                  address,
                                                                                              primary
                                                                                                  =
                                                                                                  false,
                                                                                              type =
                                                                                                  x.
                                                                                                  email_type
                                                                                          }).ToList()
                                                                 : null,
                                                    food = extended
                                                               ?
                                                                   db.person_foods.Where(
                                                                       x => x.person_id == id)
                                                                       .Select(x => x.food).ToList()
                                                               : null,
                                                    heroes = extended
                                                                 ?
                                                                     db.person_heroes.Where(
                                                                         x => x.person_id == id)
                                                                         .Select(x => x.hero).ToList()
                                                                 : null,
                                                    movies = extended
                                                                 ?
                                                                     db.person_movies.Where(
                                                                         x => x.person_id == id)
                                                                         .Select(x => x.movie).ToList()
                                                                 : null,
                                                    interests = extended
                                                                    ?
                                                                        db.person_interests.Where(
                                                                            x => x.person_id == id)
                                                                            .Select(x => x.interest).
                                                                            ToList()
                                                                    : null,
                                                    music = extended
                                                                ?
                                                                    db.person_musics.Where(
                                                                        x => x.person_id == id)
                                                                        .Select(x => x.music).ToList()
                                                                : null,
                                                    phoneNumbers = extended
                                                                       ?
                                                                           db.person_phone_numbers.
                                                                               Where(
                                                                               x => x.person_id == id)
                                                                               .Select(
                                                                               x =>
                                                                               new ListField()
                                                                                   {
                                                                                       primary = false,
                                                                                       type = x.number_type,
                                                                                       value = x.number
                                                                                   })
                                                                               .ToList()
                                                                       : null,
                                                    quotes = extended
                                                                 ?
                                                                     db.person_quotes.Where(x => x.person_id == id)
                                                                         .Select(x => x.quote).ToList()
                                                                 : null,
                                                    sports = extended
                                                                 ?
                                                                     db.person_sports.Where(x => x.person_id == id)
                                                                         .Select(x => x.sport).ToList()
                                                                 : null,
                                                    tags = extended
                                                               ? db.person_tags.Where(x => x.person_id == id)
                                                                     .Select(x => x.tag).ToList()
                                                               : null,
                                                    turnOffs = extended
                                                                   ?
                                                                       db.person_turn_offs.Where(x => x.person_id == id)
                                                                           .Select(x => x.turn_off).ToList()
                                                                   : null,
                                                    turnOns = extended
                                                                  ?
                                                                      db.person_turn_ons.Where(x => x.person_id == id)
                                                                          .Select(x => x.turn_on).ToList()
                                                                  : null,
                                                    tvShows = extended
                                                                  ?
                                                                      db.person_tv_shows.Where(x => x.person_id == id)
                                                                          .Select(x => x.tv_show).ToList()
                                                                  : null,
                                                    urls = extended
                                                               ? db.person_urls.Where(x => x.person_id == id)
                                                                     .Select(x => new ListField() {value = x.url}).
                                                                     ToList()
                                                               : null,
                                                    addresses = extended
                                                                    ? db.addresses.
                                                                          Join(db.person_addresses, a => a.id,
                                                                               b => b.address_id,
                                                                               (a, b) => new {a, b}).
                                                                          Where(x => x.b.person_id == id)
                                                                          .Select(x => new Address()
                                                                                           {
                                                                                               country = x.a.country,
                                                                                               latitude = x.a.latitude,
                                                                                               longitude = x.a.longitude,
                                                                                               locality = x.a.locality,
                                                                                               postalCode =
                                                                                                   x.a.postal_code,
                                                                                               region = x.a.region,
                                                                                               streetAddress =
                                                                                                   x.a.street_address,
                                                                                               type = x.a.address_type
                                                                                           }).ToList()
                                                                    : null,
                                                    currentLocation = extended
                                                                          ? db.addresses.
                                                                                Join(db.person_current_locations,
                                                                                     a => a.id, b => b.address_id,
                                                                                     (a, b) => new {a, b}).
                                                                                Where(x => x.b.person_id == id).
                                                                                Select(x => new Address()
                                                                                                {
                                                                                                    country = x.a.country,
                                                                                                    latitude = x.a.latitude,
                                                                                                    longitude = x.a.longitude,
                                                                                                    locality = x.a.locality,
                                                                                                    postalCode = x.a.postal_code,
                                                                                                    region = x.a.region,
                                                                                                    streetAddress = x.a.street_address,
                                                                                                    type = x.a.address_type
                                                                                                })
                                                                                .SingleOrDefault()
                                                                          : null,
                                                    organizations = extended
                                                                        ? (from j in db.person_jobs
                                                                           from s in db.person_schools
                                                                           from o in db.organizations
                                                                           where (o.id == j.organization_id && j.person_id == id) || 
                                                                                 (o.id == s.organization_id && s.person_id == id)
                                                                          select new Organization()
                                                                                               {
                                                                                                   address =
                                                                                                       new Address()
                                                                                                           {
                                                                                                               country = o.address.country,
                                                                                                               latitude = o.address.latitude,
                                                                                                               longitude = o.address.longitude,
                                                                                                               locality = o.address.locality,
                                                                                                               postalCode = o.address.postal_code,
                                                                                                               region = o.address.region,
                                                                                                               streetAddress = o.address.street_address,
                                                                                                               type = o.address.address_type
                                                                                                           },
                                                                                                   description = o.description,
                                                                                                   endDate = o.end_date.HasValue ?
                                                                                                       o.end_date.Value.FromUnixTime(): (DateTime?) null,
                                                                                                   field = o.field,
                                                                                                   name = o.name,
                                                                                                   salary = o.salary,
                                                                                                   startDate = o.start_date.HasValue?
                                                                                                       o.start_date.Value.FromUnixTime(): (DateTime?) null,
                                                                                                   subField = o.sub_field,
                                                                                                   title = o.title,
                                                                                                   webpage = o.webpage,
                                                                                                   type = j != null ? Organization.Type.Job.ToString() : Organization.Type.School.ToString()
                                                                                               })
                                                                              .ToList()
                                                                        : null
                                                };

                    add_dependency("people", id.ToString(), null);
                    data = personRaya;
                    Insert("people", id.ToString(), data);
                }
            }
            return (PersonRaya)data;
        }
        /*
	     * doing a select * on a large table is way to IO and memory expensive to do 
	     * for all friends/people on a page. So this gets just the basic fields required
	     * to build a person expression:
	     * id, email, first_name, last_name, thumbnail_url and profile_url 
	     */
        public login.login GetPersonInfo(int id)
        {
            object data;
            if (!TryGetCache("peopleInfo", id.ToString(), out data))
            {
                using (var db = RayaDataContext.DB())
                {
                    var res = (from p in db.persons
                                where p.id == id
                               select new login.login
                                           {
                                               id = p.id.ToString(),
                                               name = p.first_name + " " + p.last_name,
                                           }).SingleOrDefault();
                    if (res == null)
                    {
                        throw new Exception("Invalid person");
                    }
                    add_dependency("peopleInfo", id.ToString(),null);
                    data = res;
                    Insert("peopleInfo", id.ToString(), data);
                }
            }
            return (login.login)data;
        }

        /// <summary>
        /// get a list of new members
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        public static List<login.login> getRecentMembers(int number)
        {
            var data = new List<login.login>();
            using (var db = RayaDataContext.DB())
            {
                var result = (from p in db.persons
                              select new login.login
                              {
                                  id = p.id.ToString(),
                                  emailAddress = p.email,
                                  name = p.first_name + " " + p.last_name,
                                  fbuid = p.fb_user.fb_userid,
                                  //thumbnail_url = p.thumbnail_url,
                                  //profile_url = p.profile_url
                              }).OrderByDescending(p => p.id).Take(number);
                foreach (var entry in result)
                {
                    data.Add(entry);
                }            
            }
            return data;
        }

        public IList<user> GetConnectedFriends(long fbuid)
        {
            Facebook fbclient = new Facebook();
            fbclient.api.uid = fbuid;
            IList<user> friends;
            try
            {
                // the following gets a list of the user's friends who are also using this site
                friends = fbclient.api.friends.getAppUsersObjects();
            }
            catch (Exception)
            {
                return null;
            }
            
            return friends;
        }

        /// <summary>
        /// Get FB Connected friends that user has not befriend locally yet
        /// </summary>
        /// <param name="fbuids">connected friends</param>
        /// <returns></returns>
        public List<login.login> GetNewConnectedFBFriends(List<long> fbuids, int myuserid)
        {
            using (var db = RayaDataContext.DB())
            {
                var data = new List<login.login>();

                // gets all user's friends
                var f1 = db.friends.Where(x => x.friend_id == myuserid).Select(x => x.person_id);
                var f2 = db.friends.Where(x => x.person_id == myuserid).Select(x => x.friend_id);
                var friends = f1.Union(f2);

                var result = from p in db.persons
                             where fbuids.Contains(p.fb_user.fb_userid) && !friends.Contains(p.id)
                             select new login.login
                             {
                                 id = p.id.ToString(),
                                 emailAddress = p.email,
                                 name = p.first_name + " " + p.last_name,
                                 fbuid = p.fb_user.fb_userid
                             };
                             

                foreach (login.login user in result)
                {
                    data.Add(user);
                }
                return data;
            }
        }
        // limit = 0 = no limit
        public HashSet<login.login> GetFriends(string id)
        {
            return GetFriends(id, 0, 0);
        }
        public HashSet<login.login> GetFriends(string id, int start, int max)
        {
            object data;
            if (!TryGetCache("friendInfo", id, out data))
            {
                var ret = new HashSet<login.login>();
                using (var db = RayaDataContext.DB())
                {
                    var results = (from f in db.friends
                                  where f.person_id.ToString() == id || f.friend_id.ToString() == id
                                  select new
                                             {
                                                 f.person_id,
                                                 f.friend_id
                                             }).Skip(start);

                    if (max != 0)
                    {
                        results = results.Take(max);
                    }
                    foreach (var result in results)
                    {
                        if (result.person_id.ToString() == id)
                        {
                            ret.Add(GetPersonInfo(result.friend_id));
                            add_dependency("friendInfo", result.friend_id.ToString(), "friendInfo_res:" + id );
                        }
                        else
                        {
                            ret.Add(GetPersonInfo(result.person_id));
                            add_dependency("friendInfo", result.person_id.ToString(), "friendInfo_res:" + id);
                        }
                    }
                    data = ret;
                    Insert("friendInfo", id, data);
                }
            }
            return (HashSet<login.login>)data;
        }
        public int GetFriendsCount(string id)
        {
            using (var db = RayaDataContext.DB())
            {
                //_this->add_dependency('people', _id);
                var count = db.friends.Where(p => p.person_id.ToString() == id || p.friend_id.ToString() == id).Count();
                return count;
            }
        }
        public bool AddFriendRequest(int id, int friendId)
        {
            try 
            {
                invalidate_dependency("friendrequest", id.ToString());
                invalidate_dependency("friendrequest", friendId.ToString());
                notification req = new notification
                                       {
                                           recipient = friendId, 
                                           creator = id, 
                                           type = notifications.notifications.notifyType.friendRequest.ToInt()
                                       };
                using (var db = RayaDataContext.DB())
                {
                    db.notifications.InsertOnSubmit(req);
                    db.SubmitChanges();
                }
            } 
            catch
            {
                return false;
            }
            return true;
        }
        public void AcceptFriendRequest(string id, string friendId)
        {
            using (var db = RayaDataContext.DB())
            {
                // double check if a friend request actually exists (reversed friend/person since the request came from the other party)
                var req = (from f in db.notifications
                           where f.recipient.ToString() == id &&
                                 f.creator.ToString() == friendId &&
                                 f.type == notifications.notifications.notifyType.friendRequest.ToInt()
                           select f).SingleOrDefault();
                if (req == null)
                {
                    throw new Exception("Friend request does not exist");
                }
                db.notifications.DeleteOnSubmit(req);
                db.SubmitChanges();
                AddFriend(id, friendId);
            }
            invalidate_dependency("friendrequest", id);
            invalidate_dependency("friendrequest", friendId);
            invalidate_dependency("people", id);
            invalidate_dependency("people", friendId);
            invalidate_dependency("friendInfo", id);
            invalidate_dependency("friendInfo", friendId);
        }
        public bool RejectFriendRequest(string pid, string fid)
        {
            int id = int.Parse(pid);
            int friendId = int.Parse(fid);
            invalidate_dependency("friendrequest", pid);
            invalidate_dependency("friendrequest", fid);
            try 
            {
                using (var db = RayaDataContext.DB())
                {
                    var reqs = from f in db.notifications
                               where f.creator == friendId && 
                                       f.recipient == id &&
                                       f.type == notifications.notifications.notifyType.friendRequest.ToInt()
                               select f;
                    db.notifications.DeleteAllOnSubmit(reqs);
                    db.SubmitChanges();
                }
            } 
            catch
            {
                return false;
            }
            return true;
        }
        public List<PersonRaya> GetFriendRequests(int id)
        {
            object data;
            if (!TryGetCache("friendrequest", id.ToString(), out data))
            {
                var reqs = new List<PersonRaya>();
                
                using (var db = RayaDataContext.DB())
                {
                    var req = from f in db.notifications
                              where f.recipient == id && 
                                    f.type == notifications.notifications.notifyType.friendRequest.ToInt()
                              select f.creator;
                    foreach (var i in req)
                    {
                        reqs.Add(GetPerson(i.ToString(), false));
                        add_dependency("friendrequest", i.ToString(), "friendrequest_res:" + id);
                    }
                    data = reqs;
                    Insert("friendrequest", id.ToString(), data);
                }
            }
            return (List<PersonRaya>)data;
        }
        public HashSet<login.login> Search(string name)
        {
            var ret = new HashSet<login.login>();
            using (var db = RayaDataContext.DB())
            {
                var res = from p in db.persons
                           where (p.first_name + " " + p.last_name).Contains(name) || p.email.Contains(name)
                           select new login.login
                          {
                             id = p.id.ToString(),
                             name = p.first_name + " " + p.last_name, 
                          };
                foreach (login.login re in res)
                {
                    ret.Add(re);
                }
            }
            return ret;
        }

        public void AddFriend(string pid, string fid)
        {
            int id = int.Parse(pid);
            int friendId = int.Parse(fid);
            using (var db = RayaDataContext.DB())
            {
                // check that an entry does not already exist
                var req = from f in db.friends
                          where (f.friend_id == id && f.person_id == friendId) ||
                                (f.friend_id == friendId && f.person_id == id)
                          select f;
                if (req.Count() != 0)
                {
                    throw new Exception("Is already a friend");
                }
                // add new friend entry
                friend entry = new friend {person_id = id, friend_id = friendId};
                db.friends.InsertOnSubmit(entry);
                db.SubmitChanges();
                
                var activities = new activities.activities();
                activities.addNewFriendActivity(friendId, id);
            }
        }

        public static void save_fb_person(string id, long fbuid)
        {
            int pid = int.Parse(id);
            using (var db = RayaDataContext.DB())
            {
                fb_user u = (from f in db.fb_users
                             where f.personid == pid
                             select f).SingleOrDefault();
                if (u == null)
                {
                    u = new fb_user { personid = pid };
                    db.fb_users.InsertOnSubmit(u);
                }
                u.fb_userid = fbuid;
                db.SubmitChanges();
            }
        }

        public static void save_gfc_person(string id, string gfcUid)
        {
            int pid = int.Parse(id);
            using (var db = RayaDataContext.DB())
            {
                gfc_user u = (from f in db.gfc_users
                             where f.personid == pid
                             select f).SingleOrDefault();
                if (u == null)
                {
                    u = new gfc_user { personid = pid };
                    db.gfc_users.InsertOnSubmit(u);
                }
                u.gfc_id = gfcUid;
                db.SubmitChanges();
            }
        }
#endif
    }
}